#include "troyglview.h"

TroyGLView::TroyGLView(QWidget *parent) :
    OpenGLDisplay(parent),
    _selected(NULL)
{
    setView(THREE_QUARTER);
    //Compute the shadow matrix (just once)
    GLfloat ground[] = {0,1,0,0.01f};
    GLfloat light[] = {0,1,0,0};
    OpenGLDisplay::shadowMatrix(shadowMat, ground, light);

}


void TroyGLView::loadController(RobotGLController *controller){
    OpenGLDisplay::loadController(controller);

    //qDebug() << "Loading controller";
}

void TroyGLView::resizeGL(int width, int height){
    //Set the perspective/view
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    setPerspective(width, height);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    if(_curView == THREE_QUARTER){
        gluLookAt(5.0, 11.0, 22.0, 0.0, 9.0, 0.0, 0.0, 1.0, 0.0);
    }
    if(_curView == FRONT){
        gluLookAt(0.0, 15.0, 22.0, 0.0, 9.0, 0.0, 0.0, 1.0, 0.0);
    }
}

void TroyGLView::setPerspective(int width, int height){
    gluPerspective(70.0, ((float)width / (float)height), 1.0, 40.0);
}


void TroyGLView::initComponents(){
    GUIComponentCreator componentCreator;
    _model->accept(&componentCreator);
    _components = componentCreator.components();
}

void TroyGLView::drawScene(){
    //Draw the wireframe floor
    drawFloor();

    if(_model != NULL)
    {

        glPushMatrix();
        glColor3f(0.25f,0.25f,0.27f);
        glMultMatrixf((GLfloat *) shadowMat);
        drawRobot();
        glPopMatrix();

        glPushAttrib(GL_LIGHTING);
        glEnable(GL_LIGHTING);

        GLfloat cyan[] = {.75f, .77f, 1.0f, 1.0f};
        glMaterialfv(GL_FRONT, GL_DIFFUSE, cyan);
        glMaterialfv(GL_FRONT, GL_AMBIENT, cyan);

        glEnable(GL_DEPTH_TEST);
        drawRobot();
        glDisable(GL_DEPTH_TEST);

        glPopAttrib();

    }

    glFlush();
}


void TroyGLView::drawFloor(){
    int x,z;
    int floorSizeX = 30;
    int floorSizeZ = 30;
    int sizeX = 2;
    int sizeZ = 2;

    //Draw the wireframe floor
    glPushAttrib(GL_POLYGON_MODE);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glColor3f(0.3f,0.3f,0.3f);
    glPushMatrix();
        glTranslatef(-(floorSizeX / 2), 0, -(floorSizeZ / 2));
        glBegin(GL_QUADS);
            for(x = 0; x < (floorSizeX / sizeX); x++){
                for(z = 0; z < (floorSizeZ / sizeZ); z++){
                    glVertex3f(x * sizeX,0,z * sizeZ);
                    glVertex3f((x+1) * sizeX,0, z * sizeZ);
                    glVertex3f((x+1) * sizeX,0, (z+1) * sizeZ);
                    glVertex3f(x * sizeX,0, (z+1) * sizeZ);
                }
            }
        glEnd();
    glPopMatrix();
    glPopAttrib();
}



void TroyGLView::drawRobot(){
    //Initialize the name stack for selection.
    //Ignored by OpenGL if not in selection mode.
    glInitNames();

    //Create a ComponentGLDrawer that will visit the
    //components of the robot and draw them
    RobotGLDrawer drawer(_selected);
    _model->accept(&drawer);
}


void TroyGLView::setView(ViewOption option){
    _curView = option;
}

void TroyGLView::allowEditing(bool allowed){
    if(_controller != NULL)
        _controller->allowEditing(allowed);
}

OpenGLDisplay::GUIComponent* TroyGLView::selectComponent(NameGroup names){
    //The parent function contains most of the functionality
    _selected = OpenGLDisplay::selectComponent(names);
    updateGL();

    return _selected;
}

void TroyGLView::deselectComponent(NameGroup names){
    if(_selected != NULL){
        OpenGLDisplay::deselectComponent(names);
        _selected = NULL;
        updateGL();
    }
}

void TroyGLView::clearSelection(){
    if(_selected != NULL){
        _selected->setSelected(false);
        _selected = NULL;
        updateGL();
    }

    emit selectionCleared();
}


/*****************************************/
/*          GUICOMPONENTCREATOR          */
/*****************************************/
void TroyGLView::GUIComponentCreator::visit(RobotComponent *component){
    int i;
    OpenGLDisplay::GUIComponent* guiComponent;
    NameGroup names;
    RGBColor selectionColor;

    //Set the names
    for(i = 0; i < MAX_NAMES; i++){
        names[i] = component->names()[i];
    }

    //Set the selection color
    if(component->type() == JOINT_GROUP){
        selectionColor[0] = 1.0;
        selectionColor[1] = 0;
        selectionColor[2] = 0;
    }
    else if(component->type() == SEGMENT){
        selectionColor[0] = 0;
        selectionColor[1] = 0;
        selectionColor[2] = 1.0;
    }

    //Create the new GUIComponent object
    guiComponent = new OpenGLDisplay::GUIComponent(names, selectionColor);
    if(component->names()[0] == Troy::T_BODY)
        guiComponent->setSelectable(false);

    _components.append(guiComponent);
}

QList<OpenGLDisplay::GUIComponent*> TroyGLView::GUIComponentCreator::components(){
    return _components;
}


/*****************************************/
/*             ROBOTGLDRAWER             */
/*****************************************/
TroyGLView::RobotGLDrawer::RobotGLDrawer(GUIComponent* selected) :
    _selected(selected)
{}


void TroyGLView::RobotGLDrawer::pre(){
    glPushMatrix();
}

void TroyGLView::RobotGLDrawer::post(){
    glPopMatrix();
}


void TroyGLView::RobotGLDrawer::visit(RobotComponent* component){
    int i;
    bool selected = true;
    const unsigned int *selectionNames, *componentNames;
    const float *selectionColor;

    //Used for calculations
    QVector3D offset;
    Dimension3D dimensions;
    QQuaternion rotation;
    QVector3D rotationAxis;
    float rotationAngle, halfLengthTranslation;

    glPushAttrib(GL_COLOR);
    glPushAttrib(GL_LIGHTING);
    glPushName(component->names()[0]);

    //Translate to the offset
    offset = component->offset();
    glTranslatef(offset.x(), offset.y(), offset.z());

    dimensions = component->dimensions();
    //Translate to the center of the component along its length.
    //It is assumed that the "length" of the component is along the +y axis
    //unless component.inverted() is TRUE, in which case the length is -y
    halfLengthTranslation = (dimensions.y / 2);
    if(!component->inverted())
        halfLengthTranslation *= -1;
    glTranslatef(0.0, halfLengthTranslation, 0.0);

    //Determine if we need to draw this component as selected
    if(_selected != NULL){
        selectionNames = _selected->names();
        componentNames = component->names();
        for(i = 0; i < MAX_NAMES; i++){
            if(selectionNames[i] != componentNames[i]){
                selected = false;
                break;
            }
        }
        //The component we are drawing is the one selected in the GUI, set
        //The color to the "selection color"
        if(selected){
            selectionColor = _selected->selectionColor();
            //glColor3f(selectionColor[0], selectionColor[1], selectionColor[2]);
            GLfloat col[] = {selectionColor[0], selectionColor[1], selectionColor[2], 1.0f};
            glMaterialfv(GL_FRONT, GL_DIFFUSE, col);
            glMaterialfv(GL_FRONT, GL_AMBIENT, col);
        }
    }

    //Draw the component
    if(component->type() == JOINT_GROUP){
        OpenGLDisplay::wireSphere(dimensions.x / 2, 10, 8);
        //Rotate at this point
        rotation = ((RobotJointGroup*)(component))->rotation();
        //Convert from quaternion to axis/angle
        rotationAxis = rotation.vector();
        rotationAngle = RADIANS_TO_DEGREES(acos(rotation.scalar()) * 2.0f);
        glRotatef(rotationAngle, rotationAxis.x(), rotationAxis.y(), rotationAxis.z());
    }
    else if(component->type() == SEGMENT){
        glPushMatrix();
        glScalef(dimensions.x, dimensions.y, dimensions.z);
        OpenGLDisplay::shadedCube(1.0);
        glPopMatrix();
    }

    //We rendered everything at the center of the component,
    //translate to the end
    glTranslatef(0.0, halfLengthTranslation, 0.0);
    glPopName();
    glPopAttrib();
    glPopAttrib();
}



